package optimizers.oneplusoneES;

// author: nicolas.bredeche@upmc.fr 
// implementation of the (1+1)-ES algorithm

import fitness.BistableFitness;
import fitness.Fitness;
import gaforbistability.GAIndiv;

import java.util.Random;

import optimizers.commons.GAIndiv;
import optimizers.tools.FileManager;
import optimizers.tools.GaussianRandom;
import optimizers.tools.GenomeReader;
import optimizers.tools.Misc;

import model.Constants;
import model.SimpleSequence;
import visualization.GraphDisplayer;

public class oneplusoneES {

	final static boolean COMPLEX_FITNESS = false;
	
	int numberOfSimpleSequences;
	double x[];
	int dimensions;
	
	public static FileManager myLogFile;
	
	static int evaluation = 0;
	
	GaussianRandom grand;
	
	// ***

	private void init(double __sigma, int __numberOfSimpleSequences)
	{
		numberOfSimpleSequences = __numberOfSimpleSequences;
		
		this.dimensions = numberOfSimpleSequences*numberOfSimpleSequences*(numberOfSimpleSequences+1) + (numberOfSimpleSequences+1)*numberOfSimpleSequences + numberOfSimpleSequences + 1;

		this.x = new double[this.dimensions];

		this.grand = new GaussianRandom (0,1); // 0
		
	}
	
	// ***
	
	public double[] initGenome()
	{
		double[] x = new double[dimensions];
		
		System.out.println("init: in [-1;+1[ for sparse matrix");
		System.out.println("Genome length: " + dimensions);
		
		for ( int i = 0 ; i != dimensions ; i++ )
		{
			// this.x[i] = Math.random(); // uniform, in [0;1[
			this.x[i] = Math.random()*2.0-1.0; // uniform, in [-1;1[ ==> sparse matrix
		}
		
		return x;
	}

	// ***
	
	public double[] getMutant( double[] parent, double __sigma)
	{
		double [] child = new double[dimensions];
		
		for ( int i = 0 ; i != dimensions ; i++ )
		{
			child[i] = parent[i] + __sigma * grand.random(); 
		}

		return child;
	}
	
	// ***
	
	public double convertValue( double x )
	{
		double res;
		
		//res = x*x; // the advised method. HOWEVER this turns out to be computationally very extensive because of non-sparse matrix.
		//res = x<0?0:x ; // violent bound at zero, not classical, but in this particular case this may help as sparse matrix becomes easy to set. 
		
		if ( x > 0.5 ) // \_/, centered on 0.
			res = x - 0.5;
		else
			if ( x < -0.5 )
				res = -x - 0.5;
			else
				res = 0;
		
		return res;
	}
	

	// ***

	public double valueOf (double[] x) {
		double res = 0;
		
		double[][][] myTemplateValues = new double[numberOfSimpleSequences+1][numberOfSimpleSequences][numberOfSimpleSequences];
		double[][] mySequenceValues = new double[numberOfSimpleSequences+1][numberOfSimpleSequences];
		double[] mySeqK = new double[numberOfSimpleSequences];
		
		double exo;
		
		int index = 0;
		
		double normalizeValue;
		
		// [!!!]:
		//   - in the following, genome values are rescaled to the domain required + lower bound (x>0 mandatory for every dimension) is addressed.
		//   - "a lower bound a for a variable x is to use a + x2 instead of x in the objective function call" (cf. cmaes site)
		
		normalizeValue = 200;
		
		for (int i = 0 ; i < numberOfSimpleSequences+1 ; i++ )
			for (int j = 0 ; j < numberOfSimpleSequences ; j++ ) 
				for (int k = 0 ; k < numberOfSimpleSequences ; k++ )
				{
					myTemplateValues[i][j][k] = convertValue(x[index]) * normalizeValue;  // between 0 and 200
					index++;
				}
		
		normalizeValue = 100;
		
		for (int i = 0 ; i < numberOfSimpleSequences+1 ; i++ )
			for (int j = 0 ; j < numberOfSimpleSequences ; j++ )
			{
				mySequenceValues[i][j] = convertValue(x[index]) * normalizeValue;  // between 0 and 100
				index++;
			}

		normalizeValue = 2e1/Constants.Kduplex - 3e-2/Constants.Kduplex;
		double offsetValue = 3e-2/Constants.Kduplex;
		
		for (int i = 0 ; i < numberOfSimpleSequences ; i++ )
		{
			double value = convertValue(x[index]) * normalizeValue + offsetValue; // between 3e-2/Constants.Kduplex and 2e1/Constants.Kduplex
			
			if ( value < Constants.simpleKmin ) 
			{
				mySeqK[i] = Constants.simpleKmin;
			}
			else 
				if ( value > Constants.simpleKmax )
				{
					mySeqK[i] = Constants.simpleKmax;
				}
				else
					mySeqK[i] = value;
			
			index++;
		}

		normalizeValue = 1.0;
		exo = convertValue(x[index]) * normalizeValue; // between 0 and 1

		GAIndiv myIndividual = new GAIndiv(numberOfSimpleSequences, new Random(), myTemplateValues, mySequenceValues, mySeqK, exo, evaluation, evaluation); // pop size is 1, thus currentGen=currentInd=evaluation 
		
		myIndividual.OligoModelComplex.saturableExonuclease = false;
		myIndividual.OligoModelComplex.saturableNick = false;
		myIndividual.OligoModelComplex.saturablePoly = false;
		
		myIndividual.OligoModelComplex.seqK = mySequenceValues;
		myIndividual.OligoModelComplex.template = myTemplateValues;
		myIndividual.OligoModelComplex.exo = exo;

		GraphDisplayer displayer = new GraphDisplayer("");
		
		//displayer.addGraph(myIndividual.printVisualModel(), myIndividual.displayTimeSereisWithInput(), null);
		//displayer.display();
		//System.out.println(myIndividual.evaluateFitness());
		
		// [!!!] 1+1 ES MAXIMIZES! (DIFFERS FROM CMAES)
		
		Fitness myFitness = new BistableFitness();
		if ( COMPLEX_FITNESS == true )
			res = (myFitness.evaluateFitness(myIndividual)).finalResult(); // complex, computer-intensive fitness
		else
			res = myFitness.evaluateFitnessSimple(myIndividual).finalResult(); // Simple fitness
		
		// COMPUTE&APPLY PENALIZATION WRT SPARSITY
		double sparsity = 0;
		for ( int i = 0 ; i < x.length ; i++ )
			if ( x[i] > -0.5 && x[i] < 0.5 )
				sparsity = sparsity + 1;
		sparsity = sparsity / (double)x.length;
		res = res * sparsity;
		
		return res;
	}
	
	// ***
	
	public static void main(String[] args) {
		
		oneplusoneES optimizer = new oneplusoneES();
		
		double sigma = 1.5;
		int nbOfSimpleSequences = 6;

		int maxEvaluations = Integer.MAX_VALUE;
		
		optimizer.init(sigma,nbOfSimpleSequences);

		//double[] championGenome = optimizer.initGenome();
		//double[] championGenome = {0.0, 0.0, 0.0, 30.5, 0.0, 0.0, 0.0, 30.5, 30.5, 30.5, 0.0, 0.0, 0.0, 0.0, 30.5, 0.0, 0.0, 0.0, 0.0, 30.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 54.073857742049064, 0.0, 0.0, 0.0, 0.0, 23.91317621320531, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 30.5, 30.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 30.5, 0.0, 30.5, 0.0, 0.0, 0.0, 0.0, 30.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 30.5, 0.0, 30.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 30.5, 30.5, 0.0, 0.0, 0.0, 0.0, 0.0, 30.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 10.0, 0.01, 0.01, 0.01, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 10.0, 0.0, 0.0, 0.0, 0.0, 10.0, 0.0, 0.0, 0.0, 3.872983346207418, 3.872983346207418, 3.872983346207418, 3.872983346207418, 3.872983346207418, 3.872983346207418, 0.9662920892238617};
		double[] championGenome = GenomeReader.getGenome(2);
		
		double championFitness = optimizer.valueOf(championGenome);

		evaluation = 1;
		
		System.out.println("(1+1)-ES, sigma=" + sigma + " nbOfSimpleSequences=" + nbOfSimpleSequences + " maxEvaluations=" + maxEvaluations );

		String timeStamp = Misc.getCurrentTimeAsCompactString();

		System.out.println("Time stamp: " + timeStamp);
		System.out.println("# evaluation,currentGen,currentInd,currentFitess,bestFitnessEver");
		
		myLogFile = new FileManager("logs-optimizers/log-oneplusoneES-"+timeStamp+".txt");
		
		System.out.println(evaluation + "," + evaluation + "," + evaluation + "," + championFitness + "," + championFitness);
		myLogFile.write(evaluation + "," + evaluation+ "," + evaluation + "," + championFitness + "," + championFitness + "\n");

		myLogFile.write("# evaluation,currentGen,currentInd,currentFitess,bestFitnessEver\n");

		
		System.out.println("" + championFitness + " (first individual)");
		
		for ( int i = 0 ; i != maxEvaluations ; i++ )
		{
			System.out.print("iteration " + (i+1) + " : ");
			
			double[] challengerGenome = optimizer.getMutant(championGenome,sigma);
			double challengerFitness = optimizer.valueOf(challengerGenome);
			
			if ( challengerFitness >= championFitness ) // maximize
			{
				sigma = sigma * 2.0 ;
				championGenome = challengerGenome.clone();
				championFitness = challengerFitness;
				System.out.print("" + championFitness + " (UPDATED)");
			}
			else
			{
				sigma = sigma * 0.8408964152537145 ;
				
				double minSigmaValue = 0; //0.00000001; // 0 if unused
				if ( sigma < minSigmaValue )
					sigma = minSigmaValue;
				
				System.out.print("" + championFitness);
			}
			
			System.out.println(" (sigma: " + sigma + ")");
			
			System.out.println(evaluation + "," + evaluation + "," + evaluation + "," + challengerFitness + "," + championFitness);
			myLogFile.write(evaluation + "," + evaluation+ "," + evaluation + "," + challengerFitness + "," + championFitness + "\n");

			evaluation++;
		}

		myLogFile.close();
		
	}
	
}
